#!/usr/bin/env python

# ------------------------------------------------------------------------------
# Copyright (c) 2006 Novell, Inc. All Rights Reserved.
#
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published by the
# Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail, you may find
# current contact information at www.novell.com.
# ------------------------------------------------------------------------------

# File:		modules/Education.py
# Package:	Configuration of Education
# Summary:	Education settings, input and output functions
# Authors:	Jeff Shantz <jshantz4@csd.uwo.ca>
#
# Input and output routines for the Education module configuration.

################################################################################
# Imports
################################################################################

from time import sleep
import re
from EducationGUIHelper import *
import EducationUser
from EducationUser import *
from EducationGroup import *
import EducationFiltering
from EducationFiltering import *
from EducationAccessTimes import *
from EducationFirewalling import *
import EducationFirewalling
from YCPDeclarations import YCPDeclare
import gettext
import ycp
import pprint #TODO: Remove me
from ycp import *


gettext.install("education")

ycp.import_module("Progress")
ycp.import_module("Report")
ycp.import_module("Summary")
ycp.import_module("Message")
ycp.import_module("SCR")
from ycp import *

################################################################################
# Configuration
################################################################################

# A valid 24-hour time value
TIME_REGEX = "^([01][0-9]|2[0-3]):[0-5][0-9]$"

# The current patterns available for selection
valid_age_groups = ['children', 'teenagers']

config = {  
  'config_type': 'desktop',
  'global_filtering_enabled': False,
  'age_groups': [],
  'groups':  {},
  'users': {},
  'deleted_groups': [],
  'deleted_users': []
}

##
  # Data was modified?
  #
modified = False

##
  # Write only, used during autoinstallation.
  # Don't run services and SuSEconfig, it's all done at one place.
  #
write_only = False

################################################################################
# Functions
################################################################################

##
  # Data was modified?
  # @return true if modified
  #
@YCPDeclare("boolean")
def Modified():
  global modified
  y2debug ("modified=%d" % (modified));
  return modified

##
  # Mark as modified, for Autoyast.
  #
@YCPDeclare("void", "boolean")
def SetModified(value):
  global modified
  modified = value

@YCPDeclare("boolean")
def WriteOnly():
  global write_only
  return write_only

@YCPDeclare("void", "boolean")
def SetWriteOnly(value):
  global write_only
  write_only = value

def config_equals(key, value):
  global config
  return config[key] == value

def get_config(key):
  global config
  return config[key]

def set_config(key, value):
  global config
  config[key] = value

def config_list_has(list_key, value):
  global config
  return value in config[list_key]

def write_main_config_to_disk():

  path = ".sysconfig.education.config."

  SCR.Write(Path(path + "CONFIG_TYPE"), get_config('config_type'))
  SCR.Write(Path(path + "FILTERING_ENABLED"), get_config('global_filtering_enabled') and "yes" or "no")
  SCR.Write(Path(path + "SYSTEM_AGE_GROUPS"), ",".join(get_config('age_groups')))
  SCR.Write(Path(path + "GROUPS"), ",".join(get_config('groups').keys()))
  SCR.Write(Path(path + "USERS"), ",".join(get_config('users').keys()))
  
def read_filter(group):

  path = ".edugroup.value." + group
  filter = EducationFilter()

  filtering_enabled = SCR.Read(Path(path + ".FILTERING_ENABLED"))
  filter_tolerance  = SCR.Read(Path(path + ".FILTER_TOLERANCE"))
  filter_topics     = SCR.Read(Path(path + ".FILTER_TOPICS"))
  whitelist         = SCR.Read(Path(path + ".FILTER_WHITELIST"))
  whitelist_mode    = SCR.Read(Path(path + ".FILTER_WHITELIST_MODE"))
  blacklist         = SCR.Read(Path(path + ".FILTER_BLACKLIST"))
  banned_keywords   = SCR.Read(Path(path + ".FILTER_KEYWORDS"))
  banned_extensions = SCR.Read(Path(path + ".FILTER_EXTENSIONS"))

  filter.set_enabled(((filtering_enabled != None) and (filtering_enabled.strip().lower() == "yes")), True)

  if (filter_tolerance and re.match("^[0-9]+$", filter_tolerance)):
    filter.set_tolerance(max(EducationFiltering.MIN_TOLERANCE, int(filter_tolerance)), True)
    filter.set_tolerance(min(EducationFiltering.MAX_TOLERANCE, filter.tolerance), True)

  if (filter_topics):

    topics = filter_topics.split(",")

    for topic in topics:
      filter.add_banned_topic(topic.capitalize(), True)

  if (whitelist):

    sites = whitelist.split(",")

    for site in sites:
      url = EducationFiltering.parse_url(site)

      if (url):
        filter.add_whitelist_site(site, True)

  if (whitelist_mode) and (whitelist_mode.strip().lower() in EducationFiltering.valid_whitelist_modes):
    filter.set_whitelist_mode(whitelist_mode.strip().lower(), True)

  if (blacklist):

    sites = blacklist.split(",")

    for site in sites:
      url = EducationFiltering.parse_url(site)

      if (url):
        filter.add_blacklist_site(site, True)

  if (banned_keywords):

    keywords = banned_keywords.split(",")

    for word in keywords:
      filter.add_banned_keyword(word, True)

  if (banned_extensions):

    extensions = banned_extensions.split(",")

    for ext in extensions:      
        filter.add_banned_extension(ext, True)

  return filter
  
def read_access_restrictions(group):

  path = ".edugroup.value." + group
  restriction = AccessTimeRestrictions()

  enabled = SCR.Read(Path(path + ".ACCESS_RESTRICTIONS_ENABLED"))

  restriction.set_enabled((enabled != None) and (enabled.strip().lower() == "yes"), True)
  
  for day in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']:

    allow_access = SCR.Read(Path(path + "." + day.upper() + "_ALLOW_ACCESS"))
    start_time = SCR.Read(Path(path + "." + day.upper() + "_START_TIME"))
    end_time = SCR.Read(Path(path + "." + day.upper() + "_END_TIME"))

    if (allow_access and allow_access.strip().lower() == "yes"):
      
      restriction.set_allowed_on(day, True, True)
      start = ((start_time) and (re.match(TIME_REGEX, start_time))) and start_time or restriction.get_start_time_on(day)
      end = ((end_time) and (re.match(TIME_REGEX, end_time))) and end_time or restriction.get_end_time_on(day)
      restriction.set_access_times_on(day, start, end, True)

    else:

      restriction.set_allowed_on(day, False, True)

  return restriction

def read_firewall_rules(group):

  path = ".edugroup.value." + group
  valid_protocols = EducationFirewalling.get_protocol_key_list()
  blocked = SCR.Read(Path(path + ".BLOCKED_PROTOCOLS"))
  rules = FirewallRules()

  if (blocked):
    blocked_protocols = blocked.split(",")

    for protocol in blocked_protocols:
      if protocol in valid_protocols:
        rules.add_blocked_protocol_by_key(protocol,True)

  return rules

def read_group(group_name):

  path = ".edugroup.value." + group_name.strip()
  group = Group(group_name.strip())

  friendly_name     = SCR.Read(Path(path + ".FRIENDLY_NAME"))    # Should probably be read from /etc/group
  age_range_start   = SCR.Read(Path(path + ".AGE_RANGE_START"))
  age_range_end     = SCR.Read(Path(path + ".AGE_RANGE_END"))  
  profile_type      = SCR.Read(Path(path + ".PROFILE_TYPE"))
  sabayon_profile   = SCR.Read(Path(path + ".SABAYON_PROFILE"))
  kiosk_profile     = SCR.Read(Path(path + ".KIOSK_PROFILE"))
  
  group.friendly_name = (friendly_name or '')
  group.age_range_start = ((age_range_start and re.match("^[0-9]+$", age_range_start)) and int(age_range_start) or 0)
  group.age_range_end = ((age_range_end and re.match("^[0-9]+$", age_range_end)) and int(age_range_end) or 125)

  if (group.age_range_start) > 125:
    group.age_range_start = 125

  if group.age_range_end > 125:
    group.age_range_end = 125

  if (group.age_range_end < group.age_range_start):
    temp = group.age_range_start
    group.age_range_start = group.age_range_end
    group.age_range_end = temp

  if (profile_type) and (profile_type.strip().lower() in ['default','none','custom']):
      group.profile_type = profile_type

  group.sabayon_profile = sabayon_profile or ''
  group.kiosk_profile = kiosk_profile or ''

  if (group.profile_type == 'custom') and (group.sabayon_profile == '') and (group.kiosk_profile == ''):
    group.profile_type = 'default'
  
  return group

def read_user(username):

  path = ".eduuser.value." + username.strip()
  user = User(username.strip())

  full_name         = SCR.Read(Path(path + ".FULL_NAME"))    # Should probably be read from /etc/passwd
  group             = SCR.Read(Path(path + ".GROUP"))
  auto_login        = SCR.Read(Path(path + ".AUTO_LOGIN"))
  enabled           = SCR.Read(Path(path + ".ENABLED"))

  user.full_name = full_name or ''
  
  if (group) and get_config('groups').has_key(group):
    user.set_group(group, True)
  
  user.auto_login = (auto_login != None) and (auto_login.strip().lower() == 'yes')
  user.enabled = (enabled != None) and (enabled.strip().lower() == 'yes')

  return user

##
  # Read all Education settings
  # @return true on success
  #
@YCPDeclare("boolean")
def Read():
    
  global modified
  
  # Education read dialog caption
  caption = _("Initializing Education Configuration")

  # TODO FIXME Set the right number of stages
  steps = 3

  sl = 0.5
  sleep(sl)
    
  # TODO FIXME Names of real stages
  # We do not set help text here, because it was set outside
  ycp.Progress.New(caption, " ", steps, [
    # Progress stage 1/3
    _("Read module configuration"),
    # Progress stage 2/3
    _("Read group configuration"),
  ], [
    # Progress step 1/3
    _("Reading module configuration... "),
    # Progress step 2/3
    _("Reading group configuration..."),
    # Progress finished
    _("Finished")
  ],
    ""
  )

  # Read the main configuration
  ycp.Progress.NextStage();

  cfg_type = SCR.Read(Path(".sysconfig.education.config.CONFIG_TYPE"))
  filtering_enabled = SCR.Read(Path(".sysconfig.education.config.FILTERING_ENABLED"))
  age_groups = SCR.Read(Path(".sysconfig.education.config.SYSTEM_AGE_GROUPS"))
  groups = SCR.Read(Path(".sysconfig.education.config.GROUPS"))
  users = SCR.Read(Path(".sysconfig.education.config.USERS"))

  # Check if server mode is desired; default is desktop
  if (cfg_type) and (cfg_type.strip().lower() == 'server'):
    set_config('config_type', 'server')

  # Check if web filtering is desired; default is False
  if (filtering_enabled) and (filtering_enabled.strip().lower() == "yes"):
    set_config('global_filtering_enabled',True)  

  if (age_groups) and (len(age_groups.split(",")) > 0):

    global valid_age_groups
    age_groups = age_groups.split(",")

    # Ensure that the age groups stored are valid
    for grp in age_groups:
      grp = grp.strip().lower()

      if not grp in valid_age_groups:
        age_groups.remove(grp)

    set_config('age_groups', age_groups)

  if (groups) and (len(groups.split(",")) > 0):

    groups = groups.split(",")

    for group in groups:

      grp = read_group(group)
      grp.filter = read_filter(group)
      grp.access_times = read_access_restrictions(group)
      grp.firewall_rules = read_firewall_rules(group)

      get_config('groups')[group] = grp            

  if (users) and (len(users.split(",")) > 0):

    users = users.split(",")

    for user in users:
      usr = read_user(user)
      get_config('users')[user] = usr

  # Error message
  if False:
    ycp.Report.Error(_("Cannot read the database1."))
  sleep(sl)

  # read another database
  ycp.Progress.NextStep()
  # Error message
  if False:
    ycp.Report.Error(_("Cannot read the database2."))
  sleep(sl)

  # read current settings
  ycp.Progress.NextStage()
  # Error message
  if False:
    ycp.Report.Error(ycp.Message.CannotReadCurrentSettings())
  sleep(sl)

  # detect devices
  ycp.Progress.NextStage()
  # Error message
  if False:
    ycp.Report.Warning(_("Cannot detect devices."))
  sleep(sl)

  # Progress finished
  ycp.Progress.NextStage()
  sleep(sl)

  modified = False

  global config
  pp = pprint.PrettyPrinter(indent=4)
  pp.pprint(config)


  return True

##
  # Write all Education settings
  # @return true on success
  #
@YCPDeclare("boolean")
def Write():
  # Education read dialog caption
  caption = _("Saving Education Configuration")

  steps = 5

  sl = 0.5
  sleep(sl)

  ycp.Progress.New(caption, " ", steps, [
    # Progress stage 1/3
    _("Write main configuration"),
    # Progress stage 2/3
    _("Write group configuration"),
    # Progress step 3/3
    _("Write user configuration")
  ], [
    # Progress step 1/3
    _("Writing main configuration"),
    # Progress step 2/3
    _("Writing group configuration"),
    # Progress step 3/4
    _("Writing user configuration"),
    # Progress step 4/4
    _("Running SuSEconfig..."),
    # Progress finished
    _("Finished")
  ],
    ""
  )

  # write settings
  ycp.Progress.NextStage()

  write_main_config_to_disk()
  
    
  # Error message
  if False:
    ycp.Report.Error (_("Cannot write settings."))
  sleep(sl)

  ycp.Progress.NextStage()
  
  EducationGroup.commit_group_changes()
  EducationUser.commit_user_changes()
  EducationGroup.commit_user_group_changes()
  
  # Finalize the write
  SCR.Write(".edugroup", None)  
  SCR.Write(".eduuser", None)  
  
  # run SuSEconfig
  ycp.Progress.NextStage()
  
  # Error message
  if False:
    ycp.Report.Error(ycp.Message.SuSEConfigFailed())
  sleep(sl)

  # Progress finished
  ycp.Progress.NextStage()
  #sleep(sl)

  return True

##
  # Get all Education settings from the first parameter
  # (For use by autoinstallation.)
  # @param settings The YCP structure to be imported.
  # @return boolean True on success
  #
@YCPDeclare("boolean", "map")
def Import(map_settings):
  # TODO FIXME: your code here (fill the above mentioned variables)...
  return True

##
  # Dump the Education settings to a single map
  # (For use by autoinstallation.)
  # @return map Dumped settings (later acceptable by Import ())
  #
@YCPDeclare("map")
def Export():
  # TODO FIXME: your code here (return the above mentioned variables)...
  return {}

##
  # Create a textual summary and a list of unconfigured cards
  # @return summary of the current configuration
  #
@YCPDeclare("list")
def Summary():
  # TODO FIXME: your code here...
  # Configuration summary text for autoyast
  return []

##
  # Create an overview table with all configured cards
  # @return table items
  #
@YCPDeclare("list")
def Overview():
  # TODO FIXME: your code here...
  return []

##
  # Return packages needed to be installed and removed during
  # Autoinstallation to insure module has all needed software
  # installed.
  # @return map with 2 lists.
  #
@YCPDeclare("map")
def AutoPackages():
  # TODO FIXME: your code here...
  return {
    "install": [],
    "remove": []
  }
    
# EOF
